{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "93293ed6",
   "metadata": {},
   "source": [
    "## Function calling 函数调用\n",
    "\n",
    "\n",
    "与聊天完成 API 类似，助手 API 支持函数调用。函数调用允许您向助手描述函数，并让它智能地返回需要调用的函数及其参数。助手 API 在调用函数时将在运行期间暂停执行，你可以提供函数回调的结果以继续运行执行。\n",
    "\n",
    "### Defining functions 定义函数\n",
    "\n",
    "首先，在创建 Assistant 时定义您的函数：\n",
    "```python\n",
    "assistant = client.beta.assistants.create(\n",
    "  instructions=\"You are a weather bot. Use the provided functions to answer questions.\",\n",
    "  model=\"gpt-4-turbo-preview\",\n",
    "  tools=[{\n",
    "      \"type\": \"function\",\n",
    "    \"function\": {\n",
    "      \"name\": \"getCurrentWeather\",\n",
    "      \"description\": \"Get the weather in location\",\n",
    "      \"parameters\": {\n",
    "        \"type\": \"object\",\n",
    "        \"properties\": {\n",
    "          \"location\": {\"type\": \"string\", \"description\": \"The city and state e.g. San Francisco, CA\"},\n",
    "          \"unit\": {\"type\": \"string\", \"enum\": [\"c\", \"f\"]}\n",
    "        },\n",
    "        \"required\": [\"location\"]\n",
    "      }\n",
    "    }\n",
    "  }, {\n",
    "    \"type\": \"function\",\n",
    "    \"function\": {\n",
    "      \"name\": \"getNickname\",\n",
    "      \"description\": \"Get the nickname of a city\",\n",
    "      \"parameters\": {\n",
    "        \"type\": \"object\",\n",
    "        \"properties\": {\n",
    "          \"location\": {\"type\": \"string\", \"description\": \"The city and state e.g. San Francisco, CA\"},\n",
    "        },\n",
    "        \"required\": [\"location\"]\n",
    "      }\n",
    "    } \n",
    "  }]\n",
    ")\n",
    "```\n",
    "\n",
    "### 读取助手调用的函数\n",
    "\n",
    "当您启动触发函数的用户运行消息时，运行将进入状态 pending 。处理后，运行将进入一种 requires_action 状态，您可以通过检索运行来验证该状态。该模型可以使用并行函数调用提供多个函数一次调用：\n",
    "```\n",
    "{\n",
    "  \"id\": \"run_abc123\",\n",
    "  \"object\": \"thread.run\",\n",
    "  \"assistant_id\": \"asst_abc123\",\n",
    "  \"thread_id\": \"thread_abc123\",\n",
    "  \"status\": \"requires_action\",\n",
    "  \"required_action\": {\n",
    "    \"type\": \"submit_tool_outputs\",\n",
    "    \"submit_tool_outputs\": {\n",
    "      \"tool_calls\": [\n",
    "        {\n",
    "          \"id\": \"call_abc123\",\n",
    "          \"type\": \"function\",\n",
    "          \"function\": {\n",
    "            \"name\": \"getCurrentWeather\",\n",
    "            \"arguments\": \"{\\\"location\\\":\\\"San Francisco\\\"}\"\n",
    "          }\n",
    "        },\n",
    "        {\n",
    "          \"id\": \"call_abc456\",\n",
    "          \"type\": \"function\",\n",
    "          \"function\": {\n",
    "            \"name\": \"getNickname\",\n",
    "            \"arguments\": \"{\\\"location\\\":\\\"Los Angeles\\\"}\"\n",
    "          }\n",
    "        }\n",
    "      ]\n",
    "    }\n",
    "  },\n",
    "```\n",
    "\n",
    "### 提交函数输出\n",
    "\n",
    "然后，可以通过提交调用函数的工具输出来完成运行。传递上述 required_action 对象中的 tool_call_id 引用，以匹配每个函数调用的输出。\n",
    "```python\n",
    "run = client.beta.threads.runs.submit_tool_outputs(\n",
    "  thread_id=thread.id,\n",
    "  run_id=run.id,\n",
    "  tool_outputs=[\n",
    "      {\n",
    "        \"tool_call_id\": call_ids[0],\n",
    "        \"output\": \"22C\",\n",
    "      },\n",
    "      {\n",
    "        \"tool_call_id\": call_ids[1],\n",
    "        \"output\": \"LA\",\n",
    "      },\n",
    "    ]\n",
    ")\n",
    "```\n",
    "提交输出后，运行将进入 queued 状态，然后继续执行。\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21bb4a3f",
   "metadata": {},
   "source": [
    "```python\n",
    "ChatPromptTemplate(\n",
    "    input_variables=['agent_scratchpad', 'input'], \n",
    "    input_types={\n",
    "        'chat_history': typing.List[\n",
    "            typing.Union[\n",
    "                langchain_core.messages.ai.AIMessage, \n",
    "                langchain_core.messages.human.HumanMessage, \n",
    "                langchain_core.messages.chat.ChatMessage, \n",
    "                langchain_core.messages.system.SystemMessage, \n",
    "                langchain_core.messages.function.FunctionMessage, \n",
    "                langchain_core.messages.tool.ToolMessage\n",
    "            ]\n",
    "        ], \n",
    "        'agent_scratchpad': typing.List[\n",
    "            typing.Union[\n",
    "                langchain_core.messages.ai.AIMessage,\n",
    "                langchain_core.messages.human.HumanMessage, \n",
    "                langchain_core.messages.chat.ChatMessage, \n",
    "                langchain_core.messages.system.SystemMessage, \n",
    "                langchain_core.messages.function.FunctionMessage, \n",
    "                langchain_core.messages.tool.ToolMessage\n",
    "            ]\n",
    "        ]\n",
    "    }, \n",
    "    messages=[\n",
    "        SystemMessagePromptTemplate(\n",
    "            prompt=PromptTemplate(input_variables=[], template='You are a helpful assistant')\n",
    "        ), \n",
    "        MessagesPlaceholder(variable_name='chat_history', optional=True),\n",
    "        HumanMessagePromptTemplate(\n",
    "            prompt=PromptTemplate(\n",
    "                input_variables=['input'], template='{input}'\n",
    "            )\n",
    "        ), \n",
    "        MessagesPlaceholder(variable_name='agent_scratchpad')\n",
    "    ]\n",
    ")\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eec6bbdf",
   "metadata": {},
   "source": [
    "### agent_scratchpad\n",
    "代理提示符必须有一个“agent_scratchpad”键，该键是“消息占位符”。 中间代理动作和工具输出消息将在这里传递。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1b2e9577",
   "metadata": {},
   "source": [
    "```python\n",
    "RunnableAssign(\n",
    "    mapper={\n",
    "      agent_scratchpad: RunnableLambda(\n",
    "          lambda x: format_to_openai_function_messages(x['intermediate_steps'])\n",
    "      )\n",
    "    }\n",
    ")\n",
    "| ChatPromptTemplate(\n",
    "    input_variables=['agent_scratchpad', 'input'], \n",
    "    input_types={\n",
    "        'chat_history': typing.List[\n",
    "            typing.Union[\n",
    "                langchain_core.messages.ai.AIMessage, \n",
    "                langchain_core.messages.human.HumanMessage, \n",
    "                langchain_core.messages.chat.ChatMessage, \n",
    "                langchain_core.messages.system.SystemMessage, \n",
    "                langchain_core.messages.function.FunctionMessage, \n",
    "                langchain_core.messages.tool.ToolMessage\n",
    "            ]\n",
    "        ], \n",
    "        'agent_scratchpad': typing.List[\n",
    "            typing.Union[\n",
    "                langchain_core.messages.ai.AIMessage, \n",
    "                langchain_core.messages.human.HumanMessage, \n",
    "                langchain_core.messages.chat.ChatMessage, \n",
    "                langchain_core.messages.system.SystemMessage, \n",
    "                langchain_core.messages.function.FunctionMessage, \n",
    "                langchain_core.messages.tool.ToolMessage\n",
    "            ]\n",
    "        ]\n",
    "    }, \n",
    "    messages=[\n",
    "        SystemMessagePromptTemplate(\n",
    "            prompt=PromptTemplate(input_variables=[], template='You are a helpful assistant')\n",
    "        ), \n",
    "        MessagesPlaceholder(variable_name='chat_history', optional=True),\n",
    "        HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['input'], template='{input}')),\n",
    "        MessagesPlaceholder(variable_name='agent_scratchpad')\n",
    "    ]\n",
    ")\n",
    "| RunnableBinding(\n",
    "    bound=ChatOpenAI(\n",
    "        client=<openai.resources.chat.completions.Completions object at 0x000001A5F7692890>, \n",
    "        async_client=<openai.resources.chat.completions.AsyncCompletions object at 0x000001A59ACD2560>, \n",
    "        temperature=0.3, \n",
    "        openai_api_key=SecretStr('**********'), \n",
    "        openai_api_base='http://127.0.0.1:1234/v1', \n",
    "        openai_proxy=''\n",
    "    ), \n",
    "    kwargs={\n",
    "        'functions': [\n",
    "            {\n",
    "                'name': 'weather', \n",
    "                'description': '根据城市获取天气数据',\n",
    "                'parameters': {\n",
    "                    'properties': {\n",
    "                        '__arg1': {\n",
    "                            'title': '__arg1', \n",
    "                            'type': 'string'\n",
    "                        }\n",
    "                    }, \n",
    "                    'required': ['__arg1'], \n",
    "                    'type': 'object'\n",
    "                }\n",
    "            }\n",
    "        ]\n",
    "    }\n",
    ")\n",
    "| OpenAIFunctionsAgentOutputParser()\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "49cf9de6",
   "metadata": {},
   "source": [
    "## format_to_openai_function_messages\n",
    "\n",
    "将（AgentAction，工具输出）元组转换为 FunctionMessage。\n",
    "\n",
    "- 参数：\n",
    "\n",
    "middle_steps：LLM迄今为止采取的步骤以及观察结果\n",
    "\n",
    "- 返回：\n",
    "\n",
    "发送给 LLM 进行下一次预测的消息列表\n",
    "\n",
    "```PYTHON\n",
    "def format_to_openai_function_messages(\n",
    "    intermediate_steps: Sequence[Tuple[AgentAction, str]],\n",
    ") -> List[BaseMessage]:\n",
    "    messages = []\n",
    "\n",
    "    for agent_action, observation in intermediate_steps:\n",
    "        messages.extend(_convert_agent_action_to_messages(agent_action, observation))\n",
    "\n",
    "    return messages\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fe6432b2",
   "metadata": {},
   "source": [
    "## _convert_agent_action_to_messages\n",
    "\n",
    "将代理操作转换为消息。此代码用于根据代理操作重建原始 AI 消息。\n",
    "\n",
    "- 参数：\n",
    "\n",
    "agent_action：要转换的代理操作。\n",
    "\n",
    "- 返回：\n",
    "\n",
    "与原始工具调用相对应的 AIMessage。\n",
    "\n",
    "```PYTHON\n",
    "if isinstance(agent_action, AgentActionMessageLog):\n",
    "        return list(agent_action.message_log) + [\n",
    "            _create_function_message(agent_action, observation)\n",
    "        ]\n",
    "    else:\n",
    "        return [AIMessage(content=agent_action.log)]\n",
    "```\n",
    "\n",
    "\n",
    "## _create_function_message\n",
    "\n",
    "将代理操作和观察转换为功能消息。\n",
    "- 参数：\n",
    "    1. agent_action：来自代理的工具调用请求\n",
    "    2. 观察：工具调用的结果\n",
    "\n",
    "\n",
    "\n",
    "- 返回：与原始工具调用相对应的 FunctionMessage\n",
    "\n",
    "```python\n",
    "return FunctionMessage(\n",
    "    name=agent_action.tool,\n",
    "    content=content,\n",
    ")\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "29a5f522",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
